package abel.util;

import java.time.DateTimeException;

import org.joda.time.DateTime;

import io.netty.buffer.ByteBuf;

/**
 * This class presents a std bcd number. Every 4 bits presents a decimal number.
 * 
 * 提示时间 0x1007020313 (H)，即10年7月2日3点13分
 * 
 * @author Abl
 *
 */
public class StdBcdNumber implements Comparable<StdBcdNumber> {

    /** sample: 19 99 12 08 23 59 59. */
//    private transient byte[] decimal = new byte[0];

    /** sample: 0x19 0x99 0x12 0x08 0x23 0x59 0x59. */
//    private transient byte[] bytes = new byte[0];

    /** same as decimal: 19 99 12 08 23 59 59. */
    private String asc = "";

    /**
     * @param bytes
     */
    public StdBcdNumber() {
    }

    /**
     * @param asc
     */
    public StdBcdNumber(String asc) {
        setAsc(asc);
    }

    /**
     * @return the decimal
     */
    public byte[] toDecimal() {
//        return decimal;
        int len = asc.length();
        byte[] decimal = new byte[len / 2];

        for (int i = 0; i < len; i += 2) {
            int v = 0xFF & Integer.parseInt(asc.substring(i, 2 + i));

            decimal[i / 2] = (byte) v;
        }

        return decimal;
    }

    /**
     * This method should be called when composing packet.
     * any byte in decimal will be [0,99].
     * decimal: 20 -> byte: 0x20.
     * 
     * @param decimal
     *            the decimal to set
     */
    public StdBcdNumber populateDecimal(byte[] decimal) {
        if (decimal == null) {
            decimal = new byte[0];
        }
//        this.decimal = decimal;
        byte[] bytes = new byte[decimal.length];

        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < decimal.length; i++) {
            int v = 0xFF & decimal[i];
            if (v > 99) {
                throw new IllegalArgumentException("Invalid seck bcd number: " + v);
            }

            // 10 radix to 16 radix.
            bytes[i] = (byte) ((v / 10) * 16 + v % 10);

            // convert to asc.
            String str = "0" + v;
            if (str.length() > 2) {
                str = str.substring(1);
            }

            builder.append(str);
        }
        asc = builder.toString();

        return this;
    }

    /**
     * @return the bytes
     */
    public byte[] toBytes() {
//        return bytes;
        int len = asc.length();
        byte[] bytes = new byte[len / 2];

        for (int i = 0; i < len; i += 2) {
            int v = 0xFF & Integer.parseInt(asc.substring(i, 2 + i));

            bytes[i / 2] = (byte) (16 * (v / 10) + v % 10);
        }

        return bytes;
    }

    /**
     * This method should be called when parsing packet.
     * bytes in buf will be considered as bytes - not decimals.
     * That means - the byte can be [0,0x99].
     * 
     * @param bytes
     * @return
     */
    public StdBcdNumber populateBytes(ByteBuf buf, int size) {
        byte[] bytes = new byte[size];
        buf.readBytes(bytes);

        return populateBytes(bytes);
    }

    /**
     * This method should be called when parsing packet.
     * bytes in buf will be considered as bytes - not decimals.
     * That means - the byte can be [0,0x99].
     * byte: 0x20 -> decimal: 20.
     * 
     * @param bytes
     * @return
     */
    public StdBcdNumber populateBytes(byte[] bytes) {
        if (bytes == null) {
            bytes = new byte[0];
        }
//        this.bytes = bytes;

//        this.decimal = new byte[bytes.length];
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            int v = 0xFF & bytes[i];
            String str = "0" + Integer.toHexString(v);
            if (str.length() > 2) {
                str = str.substring(1);
            }

            if (v / 16 > 9 || v % 16 > 9) {
                throw new IllegalArgumentException("invalid std bcd number: " + str);
            }

//            decimal[i] = (byte) (10 * (v / 16) + v % 16);
            builder.append(str);
        }

        this.asc = builder.toString();

        return this;
    }

    /**
     * @return the asc
     */
    public String getAsc() {
        return asc;
    }

    /**
     * @param asc
     *            the asc to set
     */
    public void setAsc(String asc) {
        if (asc == null) {
            asc = "";
        }
        this.asc = asc;

        /*
        int len = asc.length();
        bytes = new byte[len / 2];
        decimal = new byte[len / 2];
        
        for (int i = 0; i < len; i += 2) {
            int v = 0xFF & Integer.parseInt(asc.substring(i, 2 + i));
        
            decimal[i / 2] = (byte) v;
            bytes[i / 2] = (byte) (16 * (v / 10) + v % 10);
        }
        */
    }

    /**
     * 6 bcd number C.1 date.
     * 
     * 秒 BCD 码十位 BCD 码个位
     * 分 BCD 码十位 BCD 码个位
     * 时 BCD 码十位 BCD 码个位
     * 日 BCD 码十位 BCD 码个位
     * 星期-月 星期
     * BCD 码个位
     * 月
     * BCD 码十位
     * 月
     * BCD 码个位
     * 年 BCD 码十位 BCD 码个位
     * ——星期定义如下： D5～D7 编码表示 0～7， 0：无效， 1～7 依次表示星期一至星期日。
     * 
     * @return C.1 date
     * @throws DateTimeException
     *             if fail to parse
     */
    @Deprecated
    public DateTime _toMeterC1Joda() {
        byte[] decimal = toDecimal();
        byte[] bytes = toBytes();

        if (decimal == null || decimal.length != 6) {
            throw new DateTimeException("decimal should be of 6 decimals long.");
        }

        if (decimal[0] > 59 || decimal[1] > 59 || decimal[2] > 23) {
            throw new DateTimeException("SS:MM:HH of C.1 is invalid");
        }

        DateTime now = DateTime.now();
        int yr = now.getYear() / 100 * 100 + decimal[5];
        int mth = ((bytes[4] & 0x10) >> 4) * 10 + bytes[4] & 0x0F;
        if (mth > 12) {
            throw new DateTimeException("month of C.1 is invalid");
        }

        if (decimal[3] > new DateTime(yr, mth, 1, 0, 0).monthOfYear().getMaximumValue()) {
            throw new DateTimeException("day of C.1 is invalid");
        }

        DateTime dt = new DateTime(yr, mth, decimal[3], decimal[2], decimal[1], decimal[0]);

        // ignore week.
        /*
        if (dt.getDayOfWeek() != (bytes[4] & 0xE0 >> 5)) {
            throw new DateTimeException("day of week in C.1 is invalid");
        }
        */

        return dt;
    }

    /**
     * 5 bcd number C.15 time.
     * 
     * 分 BCD 码十位 BCD 码个位
     * 时 BCD 码十位 BCD 码个位
     * 日 BCD 码十位 BCD 码个位
     * 月 BCD 码十位 BCD 码个位
     * 年 BCD 码十位 BCD 码个位
     * 
     * @return
     */
    public DateTime toMeterC15Joda() {
        byte[] decimal = toDecimal();
        byte[] bytes = toBytes();

        if (decimal == null || decimal.length != 5) {
            throw new DateTimeException("decimal should be of 5 decimals long.");
        }

        if (decimal[0] > 59 || decimal[1] > 23) {
            throw new DateTimeException("MM:HH of C.15 is invalid");
        }

        DateTime now = DateTime.now();
        int yr = now.getYear() / 100 * 100 + decimal[4];
        int mth = decimal[3];
        if (mth > 12) {
            throw new DateTimeException("month of C.15 is invalid");
        }

        if (decimal[2] > new DateTime(yr, mth, 1, 0, 0).dayOfMonth().getMaximumValue()) {
            throw new DateTimeException("day of C.15 is invalid");
        }

        return new DateTime(yr, mth, decimal[2], decimal[1], decimal[0]);
    }

    /**
     * 4 bcd number C.16 time.
     * 
     * 秒 BCD 码十位 BCD 码个位
     * 分 BCD 码十位 BCD 码个位
     * 时 BCD 码十位 BCD 码个位
     * 日 BCD 码十位 BCD 码个位
     * 
     * @return C.16 date.
     * @throws DateTimeException
     *             if fail to parse
     */
    public DateTime toMeterC16Joda() {
        byte[] decimal = toDecimal();
        byte[] bytes = toBytes();

        if (decimal == null || decimal.length != 4) {
            throw new DateTimeException("decimal should be of 4 decimals long.");
        }

        if (decimal[0] > 59 || decimal[1] > 59 || decimal[2] > 23) {
            throw new DateTimeException("SS:MM:HH of C.16 is invalid: " + asc);
        }

        DateTime now = DateTime.now();
        int yr = now.getYear();
        int mth = now.getMonthOfYear();

        if (decimal[3] > new DateTime(yr, mth, 1, 0, 0).dayOfMonth().getMaximumValue()) {
            throw new DateTimeException("day of C.16 is invalid: " + decimal[3]);
        }

        DateTime dt = new DateTime(yr, mth, decimal[3], decimal[2], decimal[1], decimal[0]);

        return dt;
    }

    /**
     * 3 bcd number C.20 date.
     * 
     * 日 BCD 码十位 BCD 码个位
     * 月 BCD 码十位 BCD 码个位
     * 年 BCD 码十位 BCD 码个位
     * 
     * @return C.20 date
     * @throws DateTimeException
     *             if fail to parse
     */
    public DateTime toMeterC20Joda() {
        byte[] decimal = toDecimal();
        byte[] bytes = toBytes();

        if (decimal == null || decimal.length != 3) {
            throw new DateTimeException("C.20 should be 3 decimals long.");
        }

        int yr = DateTime.now().getYear() / 100 * 100;
        if (decimal[0] > new DateTime(yr + decimal[2], decimal[1], 1, 0, 0).dayOfMonth().getMaximumValue()) {
            throw new DateTimeException("day of C.20 is invalid");
        }
        if (decimal[1] < 1 || decimal[1] > 12) {
            throw new DateTimeException("month of C.20 is invalid: " + decimal[1]);
        }

        return new DateTime(yr + decimal[2], decimal[1], decimal[0], 0, 0);
    }

    /**
     * 2 bcd number C.21 date.
     * 
     * 月 BCD 码十位 BCD 码个位
     * 年 BCD 码十位 BCD 码个位
     * 
     * @return C.21 date
     * @throws DateTimeException
     *             if fail to parse
     */
    public DateTime toMeterC21Joda() {
        byte[] decimal = toDecimal();
        byte[] bytes = toBytes();

        if (decimal == null || decimal.length != 2) {
            throw new DateTimeException("C.21 should be of 2 decimals long.");
        }

        int yr = DateTime.now().getYear() / 100 * 100;
        if (decimal[0] < 1 || decimal[0] > 12) {
            throw new DateTimeException("month of C.21 is invalid: " + decimal[0]);
        }

        return new DateTime(yr + decimal[1], decimal[0], 1, 0, 0);
    }

    /**
     * 6 bcd number C.1 date.
     * 
     * 秒 BCD 码十位 BCD 码个位
     * 分 BCD 码十位 BCD 码个位
     * 时 BCD 码十位 BCD 码个位
     * 日 BCD 码十位 BCD 码个位
     * 星期-月 星期
     * BCD 码个位
     * 月
     * BCD 码十位
     * 月
     * BCD 码个位
     * 年 BCD 码十位 BCD 码个位
     * ——星期定义如下： D5～D7 编码表示 0～7， 0：无效， 1～7 依次表示星期一至星期日。
     * 
     * @return
     */
    @Deprecated
    public static StdBcdNumber _fromMeterC1Joda(DateTime dt) {
        byte[] bs = new byte[] {
            (byte) dt.getSecondOfMinute(),
            (byte) dt.getMinuteOfHour(),
            (byte) dt.getHourOfDay(),
            (byte) dt.getDayOfMonth(),
//            (byte) ((dt.getDayOfWeek() << 5) | ((dt.getMonthOfYear() / 10) << 4) | (dt.getMonthOfYear() % 10)),
            (byte) dt.getMonthOfYear(),
            (byte) (dt.getYear() % 100)
        };

        return new StdBcdNumber().populateDecimal(bs);
    }

    /**
     * 5 bcd number C.15 time.
     * 
     * 分 BCD 码十位 BCD 码个位
     * 时 BCD 码十位 BCD 码个位
     * 日 BCD 码十位 BCD 码个位
     * 月 BCD 码十位 BCD 码个位
     * 年 BCD 码十位 BCD 码个位
     * 
     * @return
     */
    public static StdBcdNumber fromMeterC15Joda(DateTime dt) {
        byte[] bs = new byte[] {
            (byte) dt.getMinuteOfHour(),
            (byte) dt.getHourOfDay(),
            (byte) dt.getDayOfMonth(),
            (byte) dt.getMonthOfYear(),
            (byte) (dt.getYear() % 100)
        };

        return new StdBcdNumber().populateDecimal(bs);
    }

    /**
     * 4 bcd numbers C.16 time.
     * 
     * 秒 BCD 码十位 BCD 码个位
     * 分 BCD 码十位 BCD 码个位
     * 时 BCD 码十位 BCD 码个位
     * 日 BCD 码十位 BCD 码个位
     */
    public static StdBcdNumber fromMeterC16Joda(DateTime dt) {
        byte[] bs = new byte[] {
            (byte) dt.getSecondOfMinute(),
            (byte) dt.getMinuteOfHour(),
            (byte) dt.getHourOfDay(),
            (byte) dt.getDayOfMonth()
        };

        return new StdBcdNumber().populateDecimal(bs);
    }

    /**
     * 3 bcd number C.20 date.
     * 
     * 日 BCD 码十位 BCD 码个位
     * 月 BCD 码十位 BCD 码个位
     * 年 BCD 码十位 BCD 码个位
     */
    public static StdBcdNumber fromMeterC20Joda(DateTime dt) {
        byte[] bs = new byte[] {
            (byte) dt.getDayOfMonth(),
            (byte) dt.getMonthOfYear(),
            (byte) (dt.getYear() % 100)
        };

        return new StdBcdNumber().populateDecimal(bs);
    }

    /**
     * 2 bcd number C.21 date.
     * 
     * 月 BCD 码十位 BCD 码个位
     * 年 BCD 码十位 BCD 码个位
     */
    public static StdBcdNumber fromMeterC21Joda(DateTime dt) {
        byte[] bs = new byte[] {
            (byte) dt.getMonthOfYear(),
            (byte) (dt.getYear() % 100)
        };

        return new StdBcdNumber().populateDecimal(bs);
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof StdBcdNumber)) {
            return false;
        }

        return asc.equals(((StdBcdNumber) obj).asc);
    }

    @Override
    public int compareTo(StdBcdNumber o) {
        return this.asc.compareTo(o.asc);
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return asc.hashCode();
    }

    /**
     * Returns bytes length in this instance.
     * 
     * @return
     */
    public int length() {
        return toBytes().length;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return String.format("{asc:'%s'}", asc);
    }

}
